/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.lang;

import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @param <T>
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 */
public abstract class Enume<T extends Comparable<T>> implements Comparable<Enume<T>> {

    /**
     * @param <T>
     * @param <E>
     */
    public static abstract class This<T extends Comparable<T>, E extends This<T, E>> extends Enume<T> {

        protected This(T value) {
            super(value);
        }

        /**
         * 如果此{@link Enume}与参数对象相等，就返回true
         *
         * @param enume 参数对象
         * @return 如果此{@link Enume}与参数对象相等，就返回true
         */
        public final boolean eq(E enume) {
            return equals(enume);
        }

        /**
         * 如果此{@link Enume}与参数对象不相等，就返回true
         *
         * @param enume 参数对象
         * @return 如果此{@link Enume}与参数对象不相等，就返回true
         */
        public final boolean ne(E enume) {
            return !eq(enume);
        }

    }

    private final T value;
    private final int ordinal;
    private String name;
    private boolean defineEnume;

    private Enume(T value) {
        @SuppressWarnings("unchecked")
        Class<? extends Enume<?>> classType = (Class<? extends Enume<?>>) getClass();
        if (findConstructor(classType) == null) {
            throw new InstantiationError("Invalid enume. [" + getClass().getName() + ", " + value + "]");
        }
        this.value = value;
        ordinal = enumeCache.add(this);
        defineEnume = true;
    }

    /**
     * 返回此{@link Enume}对象的value
     *
     * @return 返回此{@link Enume}对象的value
     */
    public final T value() {
        return value;
    }

    /**
     * 返回此{@link Enume}对象的序号，如果是unknown的返回-1
     *
     * @return 返回此{@link Enume}对象的序号，如果是unknown的返回-1
     */
    public final int ordinal() {
        if (defineEnume) {
            return ordinal;
        }
        return -1;
    }

    /**
     * 返回此{@link Enume}的定义名称，非定义的返回null
     *
     * @return 返回此{@link Enume}的定义名称，非定义的返回null
     */
    public final String name() {
        if (defineEnume) {
            if (name == null) {
                Class<?> enumeClass = getClass();
                Field[] fields = enumeClass.getFields();
                for (Field field : fields) {
                    if (isEnumeField(enumeClass, field)) {
                        try {
                            if (field.get(enumeClass) == this) {
                                name = field.getName();
                                break;
                            }
                        } catch (Exception e) {
                            //
                        }
                    }
                }
            }
            return name;
        }
        return null;
    }

    /**
     * 获取全名称。比如ClassNameA.ENUME_A
     *
     * @return
     */
    public final String fullName() {
        String name = name();
        if (name != null) {
            return getClass().getSimpleName() + "." + name;
        }
        return null;
    }

    /**
     * 如果此{@link Enume}的value与参数值相等，就返回true
     *
     * @param value 参数值
     * @return 如果此{@link Enume}的value与参数值相等，就返回true
     */
    public final boolean eqValue(T value) {
        return eqValue(this, value);
    }

    /**
     * 如果此{@link Enume}的value与参数值不相等，就返回true
     *
     * @param value 参数值
     * @return 如果此{@link Enume}的value与参数值不相等，就返回true
     */
    public final boolean neValue(T value) {
        return !eqValue(this, value);
    }

    /**
     * 返回true表示为定义的Enume对象
     *
     * @return 返回true表示为定义的Enume对象
     */
    public final boolean isDefine() {
        return defineEnume;
    }

    /**
     * 返回true表示为非定义的Enume对象
     *
     * @return 返回true表示为非定义的Enume对象
     */
    public final boolean isUnknown() {
        return !isDefine();
    }

    private void unknownEnume() {
        defineEnume = false;
    }

    private void defineEnume(String name) {
        this.defineEnume = true;
        this.name = name;
    }

    @Override
    public final int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((value == null) ? 0 : value.hashCode());
        return result;
    }

    @Override
    public final boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Enume<?> other = (Enume<?>) obj;
        if (value == null) {
            return other.value == null;
        }
        return value.equals(other.value);
    }

    @Override
    public final int compareTo(Enume<T> o) {
        if (value == null) {
            if (o.value == null) {
                return 0;
            }
            return -1;
        } else if (o.value == null) {
            return 1;
        }
        return value.compareTo(o.value);
    }

    @Override
    public final String toString() {
        return String.valueOf(value);
    }

    /**
     * 判断指定的值与{@link Enume}对象的值是否相同
     *
     * @param enume {@link Enume}对象
     * @param value 指定值
     * @return 相同就返回true
     */
    public static <T extends Comparable<T>, E extends Enume<T>> boolean eqValue(E enume, T value) {
        return equalsValue(enume, value);
    }

    private static boolean equalsValue(Enume<?> enume, Object value) {
        Checks.verifyNotNull(enume, "enume");
        if (value == null) {
            return enume.value() == null;
        }
        Object enumeValue = enume.value();
        return enumeValue != null && enumeValue.equals(value);
    }

    /**
     * 判断两个{@link Enume}对象是否相同
     *
     * @param enume1 {@link Enume}对象
     * @param enume2 {@link Enume}对象
     * @return 相同就返回true
     */
    public static <T extends Comparable<T>, E extends Enume<T>> boolean eq(E enume1, E enume2) {
        if (enume1 == null) {
            return enume2 == null;
        }
        return enume1.equals(enume2);
    }

    /**
     * 获取{@link Enume}对象的值
     *
     * @param enume {@link Enume}对象
     * @return 返回{@link Enume}对象的值
     */
    public static <T extends Comparable<T>, E extends Enume<T>> T toValue(E enume) {
        return enume == null ? null : enume.value();
    }

    /**
     * 将值转为指定类型的{@link Enume}对象
     *
     * @param enumeClass {@link Enume}对象的类
     * @param value 值
     * @return 返回{@link Enume}对象
     */
    public static <T extends Comparable<T>, E extends Enume<T>> E fromValue(Class<E> enumeClass, T value) {
        return cast(valueOf(enumeClass, value));
    }

    /**
     * 将值转为指定类型的{@link Enume}对象
     *
     * @param enumeClass {@link Enume}对象的类
     * @param value 值
     * @return 返回{@link Enume}对象
     */
    public static Enume<?> valueOf(Class<? extends Enume<?>> enumeClass, Object value) {
        verifyClass(enumeClass);
        synchronized (enumeCache) {
            Enume<?> enume = enumeCache.getOne(enumeClass, value);
            if (enume == null) {
                enume = newEnume(enumeClass, value);
                enume.unknownEnume();
            }
            return enume;
        }
    }

    /**
     * 按 {@link Enume} 的值大小排序，null 排在前面
     * @param enum1 enum1
     * @param enum2 enum2
     * @return 如果两个值均为 null 则返回 0，两个值均不为 null 时，enum1 小于、等于、大于 enum2 时返回 -1、0、1
     */
    public static <T extends Comparable<T>, E extends Enume<T>> int compare(E enum1, E enum2) {
        return compare(enum1, enum2, false);
    }

    /**
     * 按 {@link Enume} 的值大小排序，null 排在后面
     * @param enum1 enum1
     * @param enum2 enum2
     * @return 如果两个值均为 null 则返回 0，两个值均不为 null 时，enum1 小于、等于、大于 enum2 时返回 -1、0、1
     */
    public static <T extends Comparable<T>, E extends Enume<T>> int compareNullLast(E enum1, E enum2) {
        return compare(enum1, enum2, true);
    }

    /**
     * 按 {@link Enume} 的值大小排序
     * @param enum1 enum1
     * @param enum2 enum2
     * @param nullLast 为 true 时 null 排在后面，否则 null 排在前面
     * @return 如果两个值均为 null 则返回 0，两个值均不为 null 时，enum1 小于、等于、大于 enum2 时返回 -1、0、1
     */
    public static <T extends Comparable<T>, E extends Enume<T>> int compare(E enum1, E enum2, boolean nullLast) {
        return Comparators.compareNull(enum1, enum2, nullLast);
    }

    /**
     * 按 {@link Enume} 的定义先后排序，null 排在前面
     * @param enum1 enum1
     * @param enum2 enum2
     * @return 如果两个值均为 null 则返回 0，两个值均不为 null 时，enum1 定义在前返回 -1，enum2 定义在前返回 1
     */
    public static <T extends Comparable<T>, E extends Enume<T>> int ordinalCompare(E enum1, E enum2) {
        return ordinalCompare(enum1, enum2, false);
    }

    /**
     * 按 {@link Enume} 的定义先后排序，null 排在后面
     * @param enum1 enum1
     * @param enum2 enum2
     * @return 如果两个值均为 null 则返回 0，两个值均不为 null 时，enum1 定义在前返回 -1，enum2 定义在前返回 1
     */
    public static <T extends Comparable<T>, E extends Enume<T>> int ordinalCompareNullLast(E enum1, E enum2) {
        return ordinalCompare(enum1, enum2, true);
    }

    /**
     * 按 {@link Enume} 的定义先后排序
     * @param enum1 enum1
     * @param enum2 enum2
     * @param nullLast 为 true 时 null 排在后面，否则 null 排在前面
     * @return 如果两个值均为 null 则返回 0，两个值均不为 null 时，enum1 定义在前返回 -1，enum2 定义在前返回 1
     */
    public static <T extends Comparable<T>, E extends Enume<T>> int ordinalCompare(E enum1, E enum2, boolean nullLast) {
        int v = Comparators.xorCompare(enum1, enum2, nullLast);
        if (v == 0 && enum1 != null && enum2 != null) {
            return Integer.compare(enum1.ordinal(), enum2.ordinal());
        }
        return v;
    }

    /**
     * 如果指定值是{@link Enume}类定义的值就返回true
     *
     * @param enumeClass {@link Enume}对象的类
     * @param value 值
     * @return 如果指定值不是{@link Enume}类定义的值就返回true
     */
    public static <T extends Comparable<T>, E extends Enume<T>> boolean isDefine(Class<E> enumeClass, T value) {
        for (E enume : values(enumeClass)) {
            if (enume.eqValue(value)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 如果指定值不是{@link Enume}类定义的值就返回true
     *
     * @param enumeClass {@link Enume}对象的类
     * @param value 值
     * @return 如果指定值不是{@link Enume}类定义的值就返回true
     */
    public static <T extends Comparable<T>, E extends Enume<T>> boolean isUnknown(Class<E> enumeClass, T value) {
        return !isDefine(enumeClass, value);
    }

    /**
     * 返回所有定义的{@link Enume}对象
     *
     * @param enumeClass {@link Enume}对象的类
     * @return 返回所有定义的{@link Enume}对象
     */
    public static <T extends Comparable<T>, E extends Enume<T>> Collection<E> values(Class<E> enumeClass) {
        verifyEnumeClass(enumeClass);
        synchronized (enumeCache) {
            Set<Enume<?>> set = enumeCache.getAllDefind(enumeClass);
            if (set == null) {
                return Collections.emptyList();
            } else {
                return cast(Collections.unmodifiableCollection(set));
            }
        }
    }

    /**
     * 如果指定的类不是有效的Enume子类，将抛出异常
     *
     * @param enumeClass 要检查的类
     */
    public static <T extends Comparable<T>, E extends Enume<T>> void verifyEnumeClass(Class<E> enumeClass) {
        verifyClass(enumeClass);
    }

    private static void verifyClass(Class<? extends Enume<?>> enumeClass) {
        Checks.verifyNotNull(enumeClass, "enumeClass");
        if (enumeClass.getSuperclass() != Enume.This.class) {
            throw new IllegalArgumentException("Superclass must be Enumerate. [" + enumeClass.getName() + "]");
        }
        if (findValueClass(enumeClass) == null) {
            throw new IllegalArgumentException("Superclass must has actual type arguments. [" + enumeClass.getName() + "]");
        }
    }

    /**
     * 如果指定类是Enume的子类，返回value的类型，否则返回null
     *
     * @param classType 类
     * @return 如果指定类是Enume的子类，返回value的类型，否则返回null
     */
    public static Class<? extends Comparable<?>> valueClass(Class<?> classType) {
        if (classType != null && Enume.class.isAssignableFrom(classType)) {
            return findValueClass(classType);
        }
        return null;
    }

    private static Class<? extends Comparable<?>> findValueClass(final Class<?> classType) {
        try {
            Type type = classType.getGenericSuperclass();
            if (type instanceof ParameterizedType) {
                ParameterizedType paramType = (ParameterizedType) type;
                if (Enume.This.class.equals(paramType.getRawType())) {
                    Type[] types = paramType.getActualTypeArguments();
                    if (types[1] == classType) {
                        return cast(types[0]);
                    }
                }
            }
        } catch (Exception e) {
            //
        }
        return null;
    }

    private static Constructor<? extends Enume<?>> findConstructor(Class<? extends Enume<?>> enumeClass) {
        Constructor<? extends Enume<?>> constructor = null;
        Class<?> valueClass = findValueClass(enumeClass);
        if (valueClass != null) {
            try {
                constructor = enumeClass.getDeclaredConstructor(valueClass);
            } catch (Exception e) {
                //
            }
        }
        return constructor;
    }

    private static Enume<?> newEnume(Class<? extends Enume<?>> enumeClass, Object value) {
        Exception exception = null;
        try {
            Constructor<? extends Enume<?>> constructor = findConstructor(enumeClass);
            if (constructor != null) {
                constructor.setAccessible(true);
                return constructor.newInstance(value);
            }
        } catch (Exception e) {
            exception = e;
        }
        throw new RuntimeException("Invalid Enume. [" + enumeClass.getName() + ", " + value + "]", exception);
    }

    private static boolean isEnumeField(Class<?> enumeClass, Field field) {
        int m = field.getModifiers();
        return field.getType() == enumeClass
                && Modifier.isPublic(m)
                && Modifier.isStatic(m)
                && Modifier.isFinal(m);
    }

    @SuppressWarnings("unchecked")
    private static <T> T cast(Object value) {
        return (T) value;
    }

    private static final EnumeCache enumeCache = new EnumeCache();

    static void printCache(PrintStream printStream) {
        printStream.println(enumeCache);
    }

    /**
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private static class EnumeCache {

        private final Map<Class<?>, EnumesHolder> enumsFinder;

        private EnumeCache() {
            this.enumsFinder = new HashMap<>();
        }

        int add(Enume<?> enume) {
            synchronized (enumsFinder) {
                Class<?> enumeClass = enume.getClass();
                EnumesHolder holder = cast(enumsFinder.get(enumeClass));
                if (holder == null) {
                    holder = new EnumesHolder();
                    enumsFinder.put(enumeClass, holder);
                }
                int ordinal = holder.size();
                holder.add(enume);
                return ordinal;
            }
        }

        Set<Enume<?>> getAllDefind(Class<? extends Enume<?>> enumeClass) {
            EnumesHolder holder = findHolder(enumeClass);
            if (holder != null) {
                return holder.defindSet;
            }
            return null;
        }

        Enume<?> getOne(Class<? extends Enume<?>> enumeClass, Object value) {
            Enume<?> enume = null;
            EnumesHolder holder = findHolder(enumeClass);
            if (holder != null) {
                enume = holder.find(value);
            }
            return enume;
        }

        private EnumesHolder findHolder(Class<? extends Enume<?>> enumeClass) {
            EnumesHolder holder = cast(enumsFinder.get(enumeClass));
            if (holder == null) {
                // 如果没有就先加载类中定义的，防止使用Enume.valueOf()方法先创建一个对象
                Set<Enume<?>> definds = new LinkedHashSet<>();
                Field[] fields = enumeClass.getFields();
                for (Field field : fields) {
                    if (isEnumeField(enumeClass, field)) {
                        try {
                            Enume<?> e = cast(field.get(enumeClass));
                            e.defineEnume(field.getName());
                            definds.add(e);
                        } catch (Exception e) {
                            //
                        }
                    }
                }
                holder = cast(enumsFinder.get(enumeClass));
                holder.completeDefind(definds);
            } else if (holder.defindSet == null) {
                Set<Enume<?>> definds = new TreeSet<>(new Comparator<Enume<?>>() {
                    @Override
                    public int compare(Enume<?> o1, Enume<?> o2) {
                        return Comparators.compare(o1.ordinal(), o2.ordinal());
                    }
                });
                for (Enume<?> e : holder.keySet()) {
                    if (e.isDefine()) {
                        definds.add(e);
                    }
                }
                holder.completeDefind(definds);
            }
            return holder;
        }

        @Override
        public String toString() {
            return "EnumeCache [" + enumsFinder + "]";
        }
    }

    /**
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private static class EnumesHolder extends WeakHashMap<Enume<?>, Object> {

        private Set<Enume<?>> defindSet;

        void completeDefind(Set<Enume<?>> defindSet) {
            this.defindSet = defindSet;
        }

        void add(Enume<?> enume) {
            Enume<?> e = find(enume.value());
            if (e != null) {
                throw new RuntimeException("The util already exists. [" + e.getClass() + ", " + e.value() + "]");
            }
            put(enume, null);
        }

        Enume<?> find(Object value) {
            for (Enume<?> e : keySet()) {
                if (Enume.equalsValue(e, value)) {
                    return e;
                }
            }
            return null;
        }

        @Override
        public String toString() {
            Map<Enume<?>, String> map = new LinkedHashMap<>();
            for (Enume<?> enume : keySet()) {
                map.put(enume, enume.name());
            }
            return map.toString();
        }

    }
}
